package snafu.implementation.worlds;

import java.awt.geom.Point2D;
import java.util.HashSet;
import java.util.Set;

import snafu.framework.agents.Agent;
import snafu.framework.exceptions.SnafuException;
import snafu.framework.worlds.Position;
import snafu.framework.worlds.Tile;
import snafu.framework.worlds.WorldMap;
import snafu.framework.worlds.WorldMapPanel;

/**
 * http://www.gamedev.net/page/resources/_/reference/programming/sweet-snippets/coordinates-in-
 * hexagon-based-tile-maps-r1800 http://wiki.gamegardens.com/Game_Boards
 * 
 * @author Fanny
 * 
 */
public class HexWorldMap extends WorldMap {
	public final static double A = 1;
	public final static double R = A / 2;
	public final static double S = R / Math.cos(Math.PI / 6);
	public final static double H = Math.sin(Math.PI / 6) * S;
	public final static double B = S + 2 * H;
	public final static double M = H / R;
	
	
	public HexWorldMap(int width, int heiht) {
		super(width, heiht);
	}
	
	@Override
	public WorldMap getPerceptionFor(Agent agent) {
		Position position = getPosition(agent);
		boolean even = position.y % 2 == 0;

		// create the perception map
		int size = (PERCEPTION_WIDTH + 1) * 2 + 1;
		WorldMap perception = new HexWorldMap(size, size);
		// add perception for neighbors
		for (Tile tile : getPerceptionNeighborhood(PERCEPTION_WIDTH, getNeighborhood(position))) {
			// calculate new tile position
			int x = tile.getPosition().x - position.x + PERCEPTION_WIDTH;
			int y = tile.getPosition().y - position.y + PERCEPTION_WIDTH;
			
			// correct y value for even rows
			if (even) 
				y++;

			// add the tile to the perception
			Tile newTile = createTileViewFor(tile, agent, new Position(x, y));
			perception.set(new Position(x, y), newTile);
		}

		// add perception for object position
		Position tilePosition = new Position(PERCEPTION_WIDTH, even ? PERCEPTION_WIDTH + 1 : PERCEPTION_WIDTH);
		perception.set(tilePosition, createTileViewFor(get(position), agent, tilePosition));

		return perception;
	}

	@Override
	public Set<Tile> getNeighborhood(Position from) {
		// create specific neighbor list
		Set<Tile> tiles = new HashSet<Tile>() {
			@Override
			public boolean add(Tile tile) {
				// skip null tiles
				if (tile == null)
					return false;
				// add others
				return super.add(tile);
			}
		};
		// neighborhood depends on row
		if (from.y % 2 == 0) {
			// even row
			tiles.add(get(new Position(from.x - 1, from.y)));
			tiles.add(get(new Position(from.x - 1, from.y - 1)));
			tiles.add(get(new Position(from.x, from.y - 1)));
			tiles.add(get(new Position(from.x + 1, from.y)));
			tiles.add(get(new Position(from.x, from.y + 1)));
			tiles.add(get(new Position(from.x - 1, from.y + 1)));
		} else {
			// odd row
			tiles.add(get(new Position(from.x - 1, from.y)));
			tiles.add(get(new Position(from.x, from.y - 1)));
			tiles.add(get(new Position(from.x + 1, from.y - 1)));
			tiles.add(get(new Position(from.x + 1, from.y)));
			tiles.add(get(new Position(from.x + 1, from.y + 1)));
			tiles.add(get(new Position(from.x, from.y + 1)));
		}
		
		return tiles;
	}
	
	@Override
	public Tile getTile(double xPixel, double yPixel) {
		// precalculations
		int xSection = (int) (xPixel / A);
		int ySection = (int) (yPixel / (H + S));
		double xSectpxl = xPixel - xSection * A;
		double ySectpxl = yPixel - ySection * (H + S);
		
		int xArray = xSection;
		int yArray = ySection;
		
		// A sections
		if (ySection % 2 == 0) {
			// left Edge
			if (ySectpxl < (H - xSectpxl * M)) {
				xArray--;
				yArray--;
			}
			
			// right Edge
			if (ySectpxl < (-H + xSectpxl * M)) {
				yArray--;
			}
		// B sections
		} else {
			// right side
			if ((xSectpxl >= R) && (ySectpxl < (2 * H - xSectpxl * M))) {
				xArray--;
				yArray--;
			}
			// left side
			if (xSectpxl < R) {
				if (ySectpxl < (xSectpxl * M)) {
					yArray--;
				} else {
					xArray--;
				}
			}
		}
		
		return get(new Position(xArray, yArray));
	}
	
	@Override
	public Tile getFrontTile(Position from, Integer direction) {
		// get current position
		int x = from.x;
		int y = from.y;
		boolean even = y % 2 == 0;
		
		switch (direction) {
		// north east
		case 0:
			x = even ? x : x + 1;
			y = y - 1;
			break;
		// east
		case 1:
			x = x + 1;
			break;
		// sout east
		case 2:
			x = even ? x : x + 1;
			y = y + 1;
			break;
		// sout west
		case 3:
			x = even ? x - 1 : x;
			y = y + 1;
			break;
		// west
		case 4:
			x = x - 1;
			break;
		// north west
		case 5:
			x = even ? x - 1 : x;
			y = y - 1;
			break;
		default:
			throw new SnafuException("unknown direction " + direction);
		}
		
		return get(new Position(x, y));
	}
	
	// direction: 0 = NE, 1 = E, 2 = SE, 3 = SW, 4 = W, 5 = NW
	@Override
	public Integer getNextLeftDirection(Integer direction) {
		return (direction + 5) % 6;
	}
	
	@Override
	public Integer getNextRightDirection(Integer direction) {
		return (direction + 1) % 6;
	}
	
	@Override
	public String output() {
		String o = "";
		for (int y = 0; y < height; y++) {
			o += y % 2 == 1 ? " " : "";
			for (int x = 0; x < width; x++) {
				o += tiles[x][y] == null ? ". " : tiles[x][y] + " ";
			}
			o += "\n";
		}
		return o;
	}
	
	@Override
	public Point2D convertPositionToPoint(Position position) {
		if (position.y % 2 == 0)
			return new Point2D.Double(position.x * A, H + S / 2 + (H + S) * position.y - B / 2);
		else
			return new Point2D.Double(R + position.x * A, H + S / 2 + (H + S) * position.y - B / 2);
	}
	
	@Override
	public Point2D getDimension() {
		return new Point2D.Double(width + R, H + (S + H) * height);
	}
	
	@Override
	public Point2D getTileSize() {
		return new Point2D.Double(A, B);
	}
	
	@Override
	public WorldMapPanel getRepresentation() {
		return new HexWorldMapPanel(this);
	}
}
